package com.czh.mock.result;

import com.czh.mock.constant.MockFileConstant;
import com.czh.mock.exceptions.FileException;
import com.czh.mock.exceptions.code.MockCodeEnum;
import com.czh.mock.result.config.ResultSqlConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 数据已SQL的格式返回
 *
 * @author manem
 */
public class MockResultSql extends AbstractMockResult {
    private static final String SQL_MULTI_ROW_TEMPLATE = "INSERT INTO %s (%s) VALUES %s;\n";
    private static final String SQL_SINGLE_ROW_TEMPLATE = "INSERT INTO %s (%s) VALUE %s;\n";
    static Logger logger = LoggerFactory.getLogger(MockResultSql.class);

    public MockResultSql(ResultSqlConfig config) {
        this.config = config;
    }

    @Override
    public ResultSqlConfig getConfig() {
        return (ResultSqlConfig) this.config;
    }

    /**
     * 指定列名、mock数据生成算法和生成算法的参数，生成用于指定列的mock数据
     *
     * @param field         列名
     * @param param         生成算法的参数
     * @param mockAlgorithm 生成算法
     * @param <T>           生成算法的参数类型
     * @param <R>           生成算法的返回值类型
     * @return 当前对象
     */
    @Override
    public <T, R> MockResultSql mockField(String field, T param, Function<T, R> mockAlgorithm) {
        super.mockField(field, param, mockAlgorithm);
        return this;
    }

    @Override
    public String getFileSuffix() {
        return ".sql";
    }

    @Override
    String getPath() {
        return MockFileConstant.MOCK_FILE_SQL_PATH;
    }

    @Override
    public void write(List<List<MockFieldSegment>> mockRowList, boolean close) {
        if (this.getConfig().isMultiRow()) {
            this.saveUseMultiRow(close);
        } else {
            this.saveUseSingleRow(close);
        }
    }

    @Override
    public String getOutputResultPath() {
        return this.getConfig().getAbsoluteFilename();
    }

    /**
     * 以单行插入语句的格式生成mock数据
     */
    private void saveUseSingleRow(boolean close) {
        String fields = this.extractFields();
        // 记录已生成的数据，达到一定数量后再批量写入文件
        List<String> rowList = new ArrayList<>();
        // 将已生成的mock数据格式化成单条SQL插入格式
        for (List<MockFieldSegment> mockRow : this.getMockRowList()) {
            String rowValue = this.mockRowAndFormatted(mockRow);
            rowList.add(String.format(SQL_SINGLE_ROW_TEMPLATE, this.getConfig().getTblName(), fields, rowValue));
        }
        this.writeRowList(rowList, close);
    }

    /**
     * 以多行插入语句的格式生成mock数据
     */
    private void saveUseMultiRow(boolean close) {
        List<String> rowValueList = new ArrayList<>();
        // 将已生成的mock数据格式化成单条SQL插入格式
        for (List<MockFieldSegment> mockRow : this.getMockRowList()) {
            rowValueList.add(this.mockRowAndFormatted(mockRow));
        }
        // 格式化成SQL语句
        this.writeRowList(
                Collections
                        .singletonList(
                                String.format(
                                        SQL_MULTI_ROW_TEMPLATE,
                                        this.getConfig().getTblName(), this.extractFields(),
                                        String.join(",", rowValueList
                                        )
                                )
                        ),
                close
        );
    }

    /**
     * 将mock的一行数据按SQL语句中value的格式拼接
     * 例：(1,2,'hello',true)
     *
     * @param mockFieldSegments mock的一行数据
     * @return sql语句中value的格式
     */
    private String mockRowAndFormatted(List<MockFieldSegment> mockFieldSegments) {
        String valueConcat = mockFieldSegments.stream().map(valueSegment -> {
            Object rs = valueSegment.getValue();
            if (rs instanceof String) {
                return String.format("'%s'", rs);
            }
            return rs.toString();
        }).collect(Collectors.joining(","));
        return "(" + valueConcat + ")";
    }

    /**
     * 将生成的mock数据写入文件，用于单行格式
     *
     * @param rows  生成的mock数据
     * @param close 是否关闭输出流
     */
    private void writeRowList(List<String> rows, boolean close) {
        try {
            this.getOutputStream(false).write(String.join("\n", rows).getBytes());
            if (close) {
                this.closeStream();
            }
        } catch (IOException e) {
            logger.error("写入文件失败", e);
            throw new FileException(MockCodeEnum.FILE_WRITE_ERROR);
        }
    }
}
